refactor: split out blog route from shared MdxRoute file#1213
refactor: split out blog route from shared MdxRoute file#1213jderochervlk merged 26 commits intomasterfrom
Conversation
…nto vlk/split-out-blog-route
- Use String.startsWith instead of String.includes for blog route filtering to avoid accidentally excluding non-blog routes that contain 'blog' as a substring - Replace JsExn.throw with JsError.throwWithMessage in BlogArticleRoute for consistency with BlogApi.res and to get proper Error objects with stack traces - Normalize path separators in MdxFile.scanDir to fix Windows compatibility where Node.Path.join2 produces backslashes
- Use String.startsWith instead of String.includes for blog route filtering to avoid accidentally excluding non-blog routes that contain 'blog' as a substring - Replace JsExn.throw with JsError.throwWithMessage in BlogArticleRoute for consistency with BlogApi.res and to get proper Error objects with stack traces - Normalize path separators in MdxFile.scanDir to fix Windows compatibility where Node.Path.join2 produces backslashes
There was a problem hiding this comment.
Pull request overview
Refactors routing so blog article pages are handled by a dedicated route instead of being a special case inside the shared MdxRoute.
Changes:
- Added
BlogArticleRouteand updated route registration to generate per-post blog article routes from the filesystem. - Introduced
MdxFileutilities and updatedMarkdownParserto strip YAML frontmatter from rendered content. - Added
rehype-rawdependency/binding and updated Vitest/browser config plus refreshed screenshot artifacts.
Reviewed changes
Copilot reviewed 12 out of 16 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| yarn.lock | Locks new transitive deps for rehype-raw and related hast/parse5 utilities. |
| vitest.config.mjs | Minor config formatting/line adjustment around Playwright provider. |
| src/MdxFile.resi | Public interface for resolving/loading/scanning .mdx files. |
| src/MdxFile.res | Implements disk path resolution, file loading, and recursive .mdx scanning for route generation. |
| src/common/MarkdownParser.res | Adds a unified plugin to strip YAML frontmatter nodes from the output content. |
| src/bindings/Rehype.res | Adds ReScript binding for rehype-raw. |
| package.json | Adds rehype-raw dependency. |
| app/routes/MdxRoute.resi | Removes blogPost from MdxRoute loader data type. |
| app/routes/MdxRoute.res | Removes blog-specific loader/rendering branches (blog handled elsewhere now). |
| app/routes/BlogArticleRoute.resi | New route interface for blog articles. |
| app/routes/BlogArticleRoute.res | New loader/view for individual blog posts using filesystem reads + ReactMarkdown. |
| app/routes.res | Registers generated blog article routes and filters blog paths out of generic MDX routes. |
| AGENTS.md | Adds additional ReScript guidance for contributors. |
| tests/screenshots/Button_.test.jsx/button-secondary-red-chromium-linux.png | Updated screenshot artifact. |
| tests/screenshots/Button_.test.jsx/button-primary-blue-chromium-linux.png | Updated screenshot artifact. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Updates the Markdown image caption test to use a new caption and image. Regenerates all related test screenshots to reflect the change.
Add detailed instructions for running and updating Vitest browser-based unit tests in the README. Remove outdated screenshot baseline PNGs from __tests__/__screenshots__.
Add guidance in README to be selective when updating screenshots.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Cloudflare deploymentDeployement ID: 81ccc9d8-e17c-42f0-b27f-b7588b741313 ⛅️ wrangler 4.63.0 (update available 4.80.0) ✨ Uploading _redirects |
Summary
Splits blog article rendering out of the monolithic
MdxRouteinto a dedicatedBlogArticleRoute, and introduces reusable infrastructure (MdxFile,MdxContent,CompiledMdx) that paves the way for splitting out the remaining routes (docs, community, syntax-lookup) and eventually removingreact-router-mdx.Motivation
MdxRoute.reshandles every MDX-backed page on the site — blog posts, docs, community pages, and syntax lookup — in a single loader and a long chain ofif/elsebranches. This makes it hard to reason about, test, and modify individual sections without risk of breaking others. Blog is the simplest case and a good first candidate to extract.What changed
New files
src/common/CompiledMdx.res/.resisrc/MdxFile.res/.resi.mdxfiles, and compile MDX via@mdx-js/mdx.src/components/MdxContent.res/.resi<MdxContent compiledMdx />React component. Evaluates compiled MDX usingrunSyncfrom@mdx-js/mdxwith proper ReScript bindings toreact/jsx-runtime(no%raw). Contains the shared component map used across all MDX pages.app/routes/BlogArticleRoute.res/.resisrc/common/MarkdownParser.resstripFrontmatterPluginto remove YAML nodes from the remark tree so frontmatter doesn't appear in rendered output.Modified files
app/routes.resBlogArticleRoute.jsx;mdxRoutesfilters outblog/*paths to avoid duplicates.app/routes/MdxRoute.res/.resiblogPost?field fromloaderDataand the blog-specific branch in both the loader and renderer.src/bindings/Rehype.resrehype-rawbinding.package.json/yarn.lockrehype-raw(used by other parts of the site, not by this route).How MDX rendering works
The blog route uses the same MDX pipeline as
react-router-mdxbut with our own code, which will make it possible to remove that dependency later:Server (loader):
Client (component):
This means custom MDX components like
<CodeTab labels={["RE", "JS"]}>work correctly — unlike the earlier approach of passing raw markdown toreact-markdown, which can't handle JSX expressions in attributes.Key design decisions
CompiledMdx.tis opaque — the.resiexposes onlytype tandfromCompileResult. You can't accidentally pass a random string where compiled MDX is expected.MdxContentowns the component map — since the same components are used across all MDX pages on the site, they live in one place rather than being duplicated in each route.%raw—MdxContentuses proper@modulebindings toreact/jsx-runtimeand@mdx-js/mdxinstead of raw JS. The jsx-runtime exports are imported as opaquejsxRuntimeValuetypes to sidestep ReScript's monomorphisation of the polymorphicReact.jsx/React.jsxssignatures.MdxFile.compileMdxcalls@mdx-js/mdxcompiledirectly, makingreact-router-mdxno longer needed for blog routes. The remaining routes still use it for now.